/**********************************************
*  Classes:   Camera
*  Desc:      Defines a camera. Derives 
*			  from TransformNode. 
*  Author:    Vaios Kalpias-Ilias (C) 2008-2009
***********************************************/

#include "Camera.h"
#include "CdnRenderer.h"
#include <stdio.h>
//------------------------------------------------
/**
* Ctor
*/
Camera::Camera() 
: m_spTarget(TransformNodePtr()),
  m_vTargetPos(0.0f, 0.0f, -1.0f),
  m_vForward(0.0f, 0.0f, -1.0f),
  m_bLookForward(true),
  m_fNearDist(1.0f),
  m_fFarDist(300.0f),
  m_fFov(45.0f)
  
{
	m_pRenderer = CdnRenderer::GetInstance();
	UpdateProjectionData();
}
//------------------------------------------------
/**
* Dtor
*/
Camera::~Camera()
{
	m_spTarget = TransformNodePtr();
}
//------------------------------------------------
/**
* Pre update step
*/
void Camera::PreUpdate()
{
	TransformNode::PreUpdate();

	// if there is a parent that might move the camera
	// then update the frustrum
	if (m_pParent)
		m_bUpdateFrustrum = true;

	// adjust forward vector
	m_vForward = -GetGlobalRotation().GetColumn(2);
	if (m_bLookForward)
	{
		m_vTargetPos = GetGlobalTranslation() + m_vForward;
		// adjust up, right
		const mth::Matrix33& kRot = GetGlobalRotation();
		m_vGlobalUp = kRot.GetColumn(1);
		m_vGlobalRight = kRot.GetColumn(0);

	}
	else 
	{
		if (m_spTarget)
		{
			// if there is a possibly moving target
			// then update the frustrum
			m_bUpdateFrustrum = true;
			m_vTargetPos = m_spTarget->GetGlobalTranslation();
		}
		else
		{
			m_bUpdateFrustrum = true;
			m_vTargetPos = m_vTargetPosSet;
		}
		
		// create rotation to look at target
		mth::Vector3 dir = m_vTargetPos - m_vGlobalPos;
		//dir.y = 0.0f;
		dir.Normalize();
		m_qLookAtQuat.FromAxisDelta(m_vForward, dir);
		// rotate camera to look at target
		SetGlobalRotation(m_qLookAtQuat * GetGlobalRotationQuat());
		// calculate new target pos after rotation
		//const mth::Matrix33& kRot = GetGlobalRotation();
		//m_qLookAtQuat.FromAxisDelta(kRot.GetColumn(1), mth::Vector3(0.0, 1.0, 0.0));
		//SetGlobalRotation(m_qLookAtQuat * GetGlobalRotationQuat());

		const mth::Matrix33& kRot2 = GetGlobalRotation();
		m_vTargetPos = m_vGlobalPos - kRot2.GetColumn(2);
		// adjust up, right
		m_vGlobalUp = kRot2.GetColumn(1);
		m_vGlobalRight = kRot2.GetColumn(0);
		
		/*
		mth::Vector3 dir = m_vTargetPos - m_vGlobalPos;
		dir.Normalize();
		mth::Vector3 right = dir.Cross(mth::Vector3(0.0, 1.0, 0.0)).Normalized();
		mth::Vector3 newUp = right.Cross(dir);
		mth::Matrix33 rot(right, newUp, dir);
		SetGlobalRotation(rot*GetGlobalRotation());
		const mth::Matrix33& kRot = GetGlobalRotation();
		m_vTargetPos = m_vGlobalPos - kRot.GetColumn(2);
		// adjust up, right
		m_vGlobalUp = kRot.GetColumn(1);
		m_vGlobalRight = kRot.GetColumn(0);
	*/
	}

	// update frustrum if necessary
	if (m_bUpdateFrustrum)
		UpdateFrustrum();
}
//------------------------------------------------
/**
* Updates projection data
*/
void Camera::UpdateProjectionData()
{
	
	m_fRatio = CdnRenderer::GetInstance()->GetAspectRatio();

	m_fHnear = 2.0f * tan (m_fFov / 2.0f) * m_fNearDist;
	m_fWnear = m_fHnear * m_fRatio;
	m_fHfar = 2.0f * tan(m_fFov / 2.0f) * m_fFarDist;
	m_fWfar = m_fHfar * m_fRatio;

	m_bUpdateProjection = true;
	m_bUpdateFrustrum = true;

}
//------------------------------------------------
/**
* Updates the camera's frustrum
*/
void Camera::UpdateFrustrum()
{
	mth::Vector3 d = (GetTargetPos() - m_vGlobalPos).Normalized();
	const mth::Vector3& up = GetGlobalUp();
	const mth::Vector3& right = GetGlobalRight();
	const mth::Vector3 fu = up*m_fHfar/2.0f;
	const mth::Vector3 nu = up*m_fHnear/2.0f;
	const mth::Vector3 fr = right*m_fWfar/2.0f;
	const mth::Vector3 nr = right*m_fWnear/2.0f;
	mth::Vector3 fc = m_vGlobalPos + d*m_fFarDist;

	mth::Vector3 ftl = fc + fu - fr;
	mth::Vector3 ftr = fc + fu + fr;
	mth::Vector3 fbl = fc - fu - fr;
	mth::Vector3 fbr = fc - fu + fr;

	mth::Vector3 nc = m_vGlobalPos + d*m_fNearDist;
	mth::Vector3 ntl = nc + nu - nr;
	mth::Vector3 ntr = nc + nu + nr;
	mth::Vector3 nbl = nc - nu - nr;
	mth::Vector3 nbr = nc - nu + nr;

	mth::Plane3 n(nbr,nbl,ntl);
	mth::Plane3 f(fbl, fbr,ftr );
	mth::Plane3 r(fbr, nbr,ntr );
	mth::Plane3 l(nbl, fbl, ftl);
	mth::Plane3 t(ntr, ntl, ftl);
	mth::Plane3 b(fbl, nbl, nbr);

	n.Normalize();
	f.Normalize();
	r.Normalize();
	l.Normalize();
	t.Normalize();
	b.Normalize();

	m_Frustrum = Frustrum3(n,f,r,l,t,b);

	m_bUpdateFrustrum = false;
}
